package com.white.report.framework.read;

import com.white.report.framework.ReportUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 抽象的统计数据读取
 * 考虑数据量问题，需要配合一系列合理的数据库表设计，关于数据库表设计我会在文档中描述
 * 使用步骤
 * 1. 开发者需要将外部传入到内部的条件对数据进行检索后，把合格的数据返回给框架
 * 2. 确定数值统计方式 平均值,合计,最大值,最小值等...
 * 3. 确定横坐标
 *
 * </p>
 *
 * @param <P> 查询条件
 * @param <G> 分类接口
 * @author white
 * @since 2022/1/21
 */
public abstract class AbstractReportReader<P, G extends ReportReaderGroup> {

    public ReportVO get(P param) {
        return get(param, 0);
    }

    /**
     * 获取统计图表视图数据
     *
     * @param param 查询条件参数
     * @param slice 分组切片数量
     * @return
     */
    public ReportVO get(P param, int slice) {
        ReportVO result = new ReportVO();
        List<G> group = getGroup(param);
        //获取统计聚合方式
        AggregateFunction aggregateFunction = getAggregateType(param);
        //是否需要进行分组
        if (group == null || group.isEmpty()) {
            List<? extends ReportReaderData> reportData = byGroup(param, null);
            //对数据进行统计
            List<ReportReaderData> reportDatas = count(param, reportData, slice, aggregateFunction);
            //设置X坐标
            result.setDateList(reportDatas.stream().map(ReportReaderData::getX).collect(Collectors.toList()));
            if (!reportData.isEmpty()) {
                //设置数据
                ReportVO.Data data = new ReportVO.Data();
                data.setData(reportDatas.stream().map(item -> vlaueFormat(param, item.getNum())).collect(Collectors.toList()));
                result.setData(Collections.singletonList(data));
            }else {
                //没有数据则设置空集合
                result.setData(new ArrayList<>());
            }
        } else {
            //设置所有类型名称
            result.setTypeList(group.stream().map(G::getName).collect(Collectors.toList()));
            List<ReportVO.Data> datas = new ArrayList<>();
            for (G g : group) {
                List<? extends ReportReaderData> reportData = byGroup(param, g);
                //对数据进行统计
                List<ReportReaderData> reportDatas = count(param, reportData, slice, aggregateFunction);
                //设置X坐标
                result.setDateList(reportDatas.stream().map(ReportReaderData::getX).collect(Collectors.toList()));
                //设置数据
                ReportVO.Data data = new ReportVO.Data();
                data.setId(g.getId());
                data.setType(g.getName());
                data.setData(reportDatas.stream().map(item -> vlaueFormat(param, item.getNum())).collect(Collectors.toList()));
                datas.add(data);
            }
            result.setData(datas);
        }
        return result;
    }

    /**
     * ReportVO 转 ReportTableVO
     *
     * @param reportVO 参数
     * @return
     */
    protected ReportTableVO getTable(ReportVO reportVO, P param) {
        //统计图数据转表格数据
        ReportTableVO result = new ReportTableVO();
        //制表时间
        result.setMakeTime(LocalDateTime.now());
        //头
        List<ReportTableVO.ReportHeaderVO> headerList = new ArrayList<>();
        for (int i = 0; i < reportVO.getDateList().size(); i++) {
            String x = reportVO.getDateList().get(i);
            headerList.add(new ReportTableVO.ReportHeaderVO().setColumnName(x).setColumnAttr("date" + (i)));
        }
        result.setHeaderList(headerList);
        //数据
        List<Map<String, Object>> dataList = new ArrayList<>();
        for (int i = 0; i < reportVO.getData().size(); i++) {
            Map<String, Object> map = new HashMap<>();
            ReportVO.Data data = reportVO.getData().get(i);
            for (int j = 0; j < data.getData().size(); j++) {
                map.put("date" + j, data.getData().get(j));
            }
            //向统计表格中的每行插入数据
            addTableRow(param, data, map);
            dataList.add(map);
        }
        result.setDataList(dataList);
        return result;
    }

    public ReportTableVO getTable(P param) {
        return getTable(param, 0);
    }

    /**
     * 获取统计表格格式的数据
     *
     * @param param 参数
     * @param slice 切面分组数量
     * @return
     */
    public ReportTableVO getTable(P param, int slice) {
        //获取统计图数据
        ReportVO reportVO = get(param, slice);
        return getTable(reportVO, param);
    }

    /**
     * 向统计表格中的每行插入数据
     *
     * @param param 入参
     * @param data  当前行原始数据
     * @param map   当前行
     */
    protected void addTableRow(P param, ReportVO.Data data, Map<String, Object> map) {
        map.put("group", data.getType());
    }

    /**
     * 根据分类获取属于这个分类组的数据
     *
     * @param param 参数
     * @param group 分类
     * @return
     */
    protected abstract List<? extends ReportReaderData> byGroup(P param, G group);

    /**
     * 获取统计所需的分类组
     *
     * @return
     */
    protected abstract List<G> getGroup(P param);

    /**
     * 获取统计聚合接口
     *
     * @return
     */
    protected abstract AggregateFunction getAggregateType(P param);

    /**
     * 获取横坐标
     *
     * @return
     */
    protected abstract List<String> getAbscissa(P param);

    /**
     * 获取当前报表统计的时间
     * 这个时间是一个可视化字符串
     * 可以自行定义时间范围或其他
     *
     * @param param 参数
     * @return
     */
    protected abstract String getReportDate(P param);

    /**
     * 对数值进行格式化处理
     *
     * @param param
     * @param value
     * @return
     */
    protected Object vlaueFormat(P param, BigDecimal value) {
        return value;
    }

    /**
     * 图表数据统计
     *
     * @param reportDatas       需要进行统计的原始数据接口
     * @param param             参数
     * @param slice             数据切片分组数
     * @param aggregateFunction 聚合接口
     * @return
     */
    List<ReportReaderData> count(P param, List<? extends ReportReaderData> reportDatas, int slice, AggregateFunction aggregateFunction) {
        //将原始数据按照x坐标进行分组
        Map<String, ? extends List<? extends ReportReaderData>> group = reportDatas.stream().collect(Collectors.groupingBy(ReportReaderData::getX));
        //对分组的数据进行汇总
        List<ReportReaderData> counts = getAbscissa(param).stream().map(key -> {
            BigDecimal num = BigDecimal.ZERO;
            List<? extends ReportReaderData> list = group.get(key);
            if (list != null) {
                num = aggregateFunction.count(list.stream().map(ReportReaderData::getNum).collect(Collectors.toList()));
            }
            BigDecimal finalNum = num;
            return new ReportReaderData() {
                @Override
                public String getX() {
                    return key;
                }

                @Override
                public BigDecimal getNum() {
                    return finalNum;
                }
            };
        }).collect(Collectors.toList());
        //按照数量进行切片分组
        if (slice > 1) {
            List<List<ReportReaderData>> lists = ReportUtils.splitToPieces(counts, slice);
            counts = lists.stream().map(item -> {
                BigDecimal num = aggregateFunction.count(item.stream().map(ReportReaderData::getNum).collect(Collectors.toList()));
                return new ReportReaderData() {
                    @Override
                    public String getX() {
                        return item.get(0).getX() + " - " + item.get(item.size() - 1).getX();
                    }

                    @Override
                    public BigDecimal getNum() {
                        return num;
                    }
                };
            }).collect(Collectors.toList());
        }
        return counts;
    }

}
